package main

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"time"
)

func main() {
	addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:502")
	if err != nil {
		fmt.Println("resolve addr failed. ", err)
		return
	}

	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		fmt.Println("listen tcp failed.", err)
		return
	}

	for {
		conn, err := listener.AcceptTCP()
		if err != nil {
			fmt.Println("accept failed.")
			continue
		}

		go handler(conn)
	}
}

type ModbusFrame struct {
	Txid  uint16
	Proto uint16
	Addr  uint8
	Fn    uint8
	Data  []byte
}

func (m *ModbusFrame) Serialize() (data []byte, err error) {
	if len(data) < len(m.Data)+8 {
		fmt.Println("not enough buffer.")
		err = fmt.Errorf("modbus frame serialize, not enough buffer.")
		return
	}
	if m.Proto != 0 {
		fmt.Println("protocol not equal 0.")
	}
	binary.BigEndian.PutUint16(data, m.Txid)
	binary.BigEndian.PutUint16(data[2:], 0)
	binary.BigEndian.PutUint16(data[4:], uint16(2+len(m.Data)))
	data[6] = m.Addr
	data[7] = m.Fn
	copy(data[8:], m.Data)
	return
}

func (m *ModbusFrame) Deserialize(data []byte) (err error) {
	if len(data) < 6 {
		fmt.Println("frame length too small.")
		err = fmt.Errorf("input stream only %d", len(data))
		return
	}
	m.Txid = binary.BigEndian.Uint16(data)
	m.Proto = binary.BigEndian.Uint16(data[2:])
	length := binary.BigEndian.Uint16(data[4:])
	if int(length+4) != len(data) {
		fmt.Println("length error")
		err = fmt.Errorf("all %d, but length is %d", len(data), length)
		return
	}
	m.Addr = data[6]
	m.Fn = data[7]
	copy(m.Data, data[8:])
	return
}

func (m *ModbusFrame) Show() {
	fmt.Printf("TransactionID: %d\n", m.Txid)
	fmt.Println("ProtocolID: ", m.Proto)
	fmt.Println("Address: ", m.Addr)
	fmt.Println("Func Code: ", m.Fn)
	fmt.Printf("Data: %v", m.Data)
}

func rcvHandler(conn *net.TCPConn, ch chan []byte) {
	headBuf := make([]byte, 6, 64)
	for {
		_, err := io.ReadFull(conn, headBuf)
		if err != nil {
			fmt.Println("recv header from client failed.")
			close(ch)
			return
		}
		length := binary.BigEndian.Uint16(headBuf[4:])

		bodyBuf := make([]byte, length+6)
		_, err = io.ReadFull(conn, bodyBuf[6:])
		if err != nil {
			fmt.Println("recv body from client failed.")
			close(ch)
			return
		}
		copy(bodyBuf, headBuf)
		ch <- bodyBuf
	}
}

func handler(conn *net.TCPConn) {
	ChIn := make(chan []byte)
	ChOut := make(chan []byte)

	fmt.Println("recv ", conn.RemoteAddr().String())
	go rcvHandler(conn, ChIn)
	for {
		select {
		case vIn, ok := <-ChIn:
			fmt.Println("chIn.", vIn)
			if !ok {
				fmt.Println("chIn closed.")
				return
			}
		case vOut, ok := <-ChOut:
			fmt.Println("ChOut.", vOut)
			if !ok {
				fmt.Println("chout closed.")
				return
			}
		case <-time.After(10 * time.Second):
			fmt.Println("timeout...")
		}
	}
}
